unit LangMgr;

(* ======================================================== *)
(* TLangMgr                                                 *)
(* ======================================================== *)
(*   globally manager the multi language, any class want to *)
(* support multi language should implement the interface    *)
(* ILangIntf, and then reference the TLangObject or         *)
(* TLangForm.                                               *)
(*   tony                                                   *)
(*   2003.03.17                                             *)
(* ======================================================== *)

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ComCtrls, DBGrids, IniFiles, TypInfo, Registry;

type
  ILangIntf = interface
    procedure DoChangeLanguage();
    function Translate(Ident:String;Default:String):String;overload;
    function Translate(Ident:String;Default:String;const Args:array of const):String;overload;
  end;

  PLangItem = ^TLangItem;
  TLangItem = record
    LangName:String;
    FileName:String;
  end;

  TLangMgr = class(TObject)
  private
    ALangIndex:Integer;
    ALangList:TList;
    ALangObjList:TList;
    function GetLangName(FileName:String):String;
    function IsLangFile(FileName:String):Boolean;
    function GetDefaultLangExt():String;
    function GetSavedLangExt():String;
    procedure SaveLangExt(Ext:String);
    procedure InitLangList();
  public
    constructor Create();
    destructor Destroy();override;
    procedure GetLanguageList(LangList:TStringList;var LangIndex:Integer);
    procedure AddLangObj(LangObj:ILangIntf);
    procedure RemoveLangObj(LangObj:ILangIntf);
    procedure ChangeLanguage(LangIndex:Integer);
    function GetActiveLangFileName():String;
  end;

var
  ALangMgr:TLangMgr;

implementation

const
  RegistryPath = '\Software\Tony Workshop\LangManager';

{ TLangMgr }

//get the language's name from ini file
//2003.03.17
function TLangMgr.GetLangName(FileName:String):String;
var
  Ini:TIniFile;
begin
  Ini:=TIniFile.Create(FileName);
  try
    Result:=Ini.ReadString('General','Language','<Unknown>');
  finally
    Ini.Free;
  end;
end;

//test if file is a valid language file
//2003.03.17
function TLangMgr.IsLangFile(FileName:String):Boolean;
var
  Ini:TIniFile;
begin
  Ini:=TIniFile.Create(FileName);
  try
    Result:=Ini.SectionExists('General');
  finally
    Ini.Free;
  end;
end;

//get the system's default language's name, return it as a file ext name
//2003.03.17
function TLangMgr.GetDefaultLangExt():String;
var
  LocaleName: array[0..4] of Char;
begin
  GetLocaleInfo(SysLocale.DefaultLCID,LOCALE_SABBREVLANGNAME,LocaleName,SizeOf(LocaleName));
  Result:='.'+LocaleName;
end;

//get the last saved language file's ext name
//2003.03.17
function TLangMgr.GetSavedLangExt():String;
var
  Reg:TRegistry;
begin
  Reg:=TRegistry.Create();
  try
    Reg.RootKey:=HKEY_CURRENT_USER;
    if not Reg.OpenKey(RegistryPath,false) then begin
      exit;
    end;
    if Reg.ValueExists(ExtractFileName(Application.ExeName)) then begin
      Result:=Reg.ReadString(ExtractFileName(Application.ExeName));
    end;
  finally
    Reg.Free;
  end;
end;

//save the current language file's ext to registry
//2003.03.17
procedure TLangMgr.SaveLangExt(Ext:String);
var
  Reg:TRegistry;
begin
  Reg:=TRegistry.Create();
  try
    Reg.RootKey:=HKEY_CURRENT_USER;
    if not Reg.OpenKey(RegistryPath,true) then begin
      exit;
    end;
    Reg.WriteString(ExtractFileName(Application.ExeName),Ext);
    Reg.CloseKey;
  finally
    Reg.Free;
  end;
end;

//search the application path for language file, store them in the list
//2003.03.17
procedure TLangMgr.InitLangList();
var
  Sr:TSearchRec;
  FileName:string;
  LangItem:PLangItem;
begin
  ALangList.Clear;
  if FindFirst(ChangeFileExt(Application.ExeName,'.*'),faAnyFile,Sr) = 0 then begin
    repeat
      FileName:=ExtractFilePath(Application.ExeName)+Sr.Name;
      if IsLangFile(FileName) then begin
        new(LangItem);
        LangItem^.LangName:=GetLangName(FileName);
        LangItem^.FileName:=FileName;
        ALangList.Add(LangItem);
      end;
    until FindNext(Sr) <> 0;
    FindClose(Sr);
  end;
end;

//constructor
//2003.03.17
constructor TLangMgr.Create();
var
  I:Integer;
  LangItem:PLangItem;
  DefaultExt:String;
begin
  inherited;
  ALangList:=TList.Create();
  ALangObjList:=TList.Create();
  InitLangList();
  DefaultExt:=GetSavedLangExt();
  if Length(DefaultExt)=0 then
    DefaultExt:=GetDefaultLangExt();
  ALangIndex:=-1;
  for i:=0 to ALangList.Count-1 do begin
    LangItem:=ALangList.Items[i];
    if CompareText(ExtractFileExt(LangItem^.FileName),DefaultExt)=0 then begin
      ALangIndex:=i;
      exit;
    end;
  end;
end;

//destructor
//2003.03.17
destructor TLangMgr.Destroy();
var
  I:Integer;
  LangItem:PLangItem;
begin
  for I:=0 to ALangList.Count-1 do begin
    LangItem:=ALangList.Items[i];
    dispose(LangItem);
  end;
  ALangList.Free;
  if ALangObjList.Count>0 then begin
    raise Exception.Create('unsafe free of Language Manager!');
  end;
  ALangObjList.Free;
  inherited;
end;

//return the list of language file name, and the active language index
//2003.03.17
procedure TLangMgr.GetLanguageList(LangList:TStringList;var LangIndex:Integer);
var
  I:Integer;
  LangItem:PLangItem;
begin
  LangIndex:=-1;
  LangList.BeginUpdate;
  try
    LangList.Clear;
    for i:=0 to ALangList.Count-1 do begin
      LangItem:=ALangList.Items[i];
      LangList.Add(LangItem^.LangName);
    end;
    LangIndex:=ALangIndex;
  finally
    LangList.EndUpdate;
  end;
end;

//record all assigned language object, when change language, pdate them all
//2003.03.17
procedure TLangMgr.AddLangObj(LangObj:ILangIntf);
begin
  ALangObjList.Add(Pointer(LangObj));
end;

//remove an language object from list
//2003.03.17
procedure TLangMgr.RemoveLangObj(LangObj:ILangIntf);
begin
  ALangObjList.Remove(Pointer(LangObj));
end;

//change to another language, update all language object
//2003.03.17
procedure TLangMgr.ChangeLanguage(LangIndex:Integer);
var
  LangItem:PLangItem;
  I:Integer;
  LangObj:ILangIntf;
begin
  if (LangIndex>=0) and (LangIndex<ALangList.Count) then begin
    ALangIndex:=LangIndex;
    LangItem:=ALangList.Items[ALangIndex];
    SaveLangExt(ExtractFileExt(LangItem^.FileName));
  end;
  for I:=0 to ALangObjList.Count-1 do begin
    LangObj:=ILangIntf(ALangObjList.Items[I]);
    LangObj.DoChangeLanguage();
  end;
end;

//get the active lang filename
//2003.03.17
function TLangMgr.GetActiveLangFileName():String;
var
  LangItem:PLangItem;
begin
  Result:='';
  if (ALangIndex>=0) and (ALangIndex<ALangList.Count) then begin
    LangItem:=ALangList.Items[ALangIndex];
    Result:=LangItem^.FileName;
  end;
end;

initialization
  ALangMgr:=TLangMgr.Create();

finalization
  ALangMgr.Free;
  
end.
